#include <rtthread.h>
#include <rthw.h>
#include "board.h"
#include "drv_clock.h"
#include "drv_lcd.h"
#include "reg_de.h"
#include "reg-tcon.h"
#include "drv_gpio.h"

enum use_buff { FRONT, BACK };
enum state_buff { EMPTY, FULL };

struct buff_info
{
    rt_uint32_t *buff;
    rt_uint32_t *buff_phy;
    enum state_buff status;
};

/* kind of a candidate for the official lcd driver framework */
struct lcd_device
{
    struct rt_device lcd;
    struct rt_device_graphic_info lcd_info;     /* rtdef.h */
	struct fb_t113_rgb_pdata_t * pdat;
    struct rt_semaphore lcd_sem;
    int use_screen;                 /* layer index */
    void *framebuffer;
    void *framebuffer_phy;
};
typedef struct lcd_device *lcd_device_t;

static rt_uint32_t memcp_tick;
struct lcd_device _lcd_device;
static struct tcon_panel* _panel;  

#define DEFAULT_SCREEN          (0)
#define LCD_LAYER_WIDTH         (_panel->width)
#define LCD_LAYER_HEIGHT        (_panel->height)

#define LCD_DRV_FB_SZ       (LCD_LAYER_WIDTH * LCD_LAYER_HEIGHT * sizeof(rt_uint32_t))

static struct tcon_panel lcd_config = DEFAULT_LCD_CONFIG;


struct fb_t113_rgb_pdata_t
{
	uint32_t virt_de;
	uint32_t virt_tconlcd;

	char * clk_de;
	char * clk_tconlcd;
	int rst_de;
	int rst_tconlcd;
	int width;
	int height;
	int bits_per_pixel;
	int bytes_per_pixel;
	int pixlen;
	int index;
	void * vram[2];

	struct {
		int pixel_clock_hz;
		int h_front_porch;
		int h_back_porch;
		int h_sync_len;
		int v_front_porch;
		int v_back_porch;
		int v_sync_len;
		int h_sync_active;
		int v_sync_active;
		int den_active;
		int clk_active;
	} timing;
};

static void inline t113_de_enable(struct fb_t113_rgb_pdata_t * pdat)
{
	struct de_glb_t * glb = (struct de_glb_t *)(pdat->virt_de + T113_DE_MUX_GLB);
	write32((virtual_addr_t)&glb->dbuff, 1);
}

static inline void t113_de_set_address(struct fb_t113_rgb_pdata_t * pdat, void * vram)
{
	struct de_ui_t * ui = (struct de_ui_t *)(pdat->virt_de + T113_DE_MUX_CHAN + 0x1000 * 1);
	write32((virtual_addr_t)&ui->cfg[0].top_laddr, (uint32_t)(unsigned long)vram);
}

static inline void t113_de_set_mode(struct fb_t113_rgb_pdata_t * pdat)
{
	struct de_clk_t * clk = (struct de_clk_t *)(pdat->virt_de);
	struct de_glb_t * glb = (struct de_glb_t *)(pdat->virt_de + T113_DE_MUX_GLB);
	struct de_bld_t * bld = (struct de_bld_t *)(pdat->virt_de + T113_DE_MUX_BLD);
	struct de_ui_t * ui = (struct de_ui_t *)(pdat->virt_de + T113_DE_MUX_CHAN + 0x1000 * 1);
	uint32_t size = (((pdat->height - 1) << 16) | (pdat->width - 1));
	uint32_t val;
	int i;

	val = read32((virtual_addr_t)&clk->rst_cfg);
	val |= 1 << 0;
	write32((virtual_addr_t)&clk->rst_cfg, val);

	val = read32((virtual_addr_t)&clk->gate_cfg);
	val |= 1 << 0;
	write32((virtual_addr_t)&clk->gate_cfg, val);

	val = read32((virtual_addr_t)&clk->bus_cfg);
	val |= 1 << 0;
	write32((virtual_addr_t)&clk->bus_cfg, val);

	val = read32((virtual_addr_t)&clk->sel_cfg);
	val &= ~(1 << 0);
	write32((virtual_addr_t)&clk->sel_cfg, val);

	write32((virtual_addr_t)&glb->ctl, (1 << 0));
	write32((virtual_addr_t)&glb->status, 0);
	write32((virtual_addr_t)&glb->dbuff, 1);
	write32((virtual_addr_t)&glb->size, size);

	for(i = 0; i < 4; i++)
	{
		void * chan = (void *)(pdat->virt_de + T113_DE_MUX_CHAN + 0x1000 * i);
		memset(chan, 0, i == 0 ? sizeof(struct de_vi_t) : sizeof(struct de_ui_t));
	}
	memset(bld, 0, sizeof(struct de_bld_t));

	write32((virtual_addr_t)&bld->fcolor_ctl, 0x00000101);
	write32((virtual_addr_t)&bld->route, 1);
	write32((virtual_addr_t)&bld->premultiply, 0);
	write32((virtual_addr_t)&bld->bkcolor, 0xff000000);
	write32((virtual_addr_t)&bld->bld_mode[0], 0x03010301);
	write32((virtual_addr_t)&bld->bld_mode[1], 0x03010301);
	write32((virtual_addr_t)&bld->output_size, size);
	write32((virtual_addr_t)&bld->out_ctl, 0);
	write32((virtual_addr_t)&bld->ck_ctl, 0);
	for(i = 0; i < 4; i++)
	{
		write32((virtual_addr_t)&bld->attr[i].fcolor, 0xff000000);
		write32((virtual_addr_t)&bld->attr[i].insize, size);
	}

	write32(pdat->virt_de + T113_DE_MUX_VSU, 0);
	write32(pdat->virt_de + T113_DE_MUX_GSU1, 0);
	write32(pdat->virt_de + T113_DE_MUX_GSU2, 0);
	write32(pdat->virt_de + T113_DE_MUX_GSU3, 0);
	write32(pdat->virt_de + T113_DE_MUX_FCE, 0);
	write32(pdat->virt_de + T113_DE_MUX_BWS, 0);
	write32(pdat->virt_de + T113_DE_MUX_LTI, 0);
	write32(pdat->virt_de + T113_DE_MUX_PEAK, 0);
	write32(pdat->virt_de + T113_DE_MUX_ASE, 0);
	write32(pdat->virt_de + T113_DE_MUX_FCC, 0);
	write32(pdat->virt_de + T113_DE_MUX_DCSC, 0);

	write32((virtual_addr_t)&ui->cfg[0].attr, (1 << 0) | (4 << 8) | (1 << 1) | (0xff << 24));
	write32((virtual_addr_t)&ui->cfg[0].size, size);
	write32((virtual_addr_t)&ui->cfg[0].coord, 0);
	write32((virtual_addr_t)&ui->cfg[0].pitch, 4 * pdat->width);
	write32((virtual_addr_t)&ui->cfg[0].top_laddr, (uint32_t)(unsigned long)pdat->vram[pdat->index]);
	write32((virtual_addr_t)&ui->ovl_size, size);
}

static void t113_tconlcd_enable(struct fb_t113_rgb_pdata_t * pdat)
{
	struct t113_tconlcd_reg_t * tcon = (struct t113_tconlcd_reg_t *)pdat->virt_tconlcd;
	uint32_t val;

	val = read32((virtual_addr_t)&tcon->gctrl);
	val |= (1 << 31);
	write32((virtual_addr_t)&tcon->gctrl, val);
}

static void t113_tconlcd_disable(struct fb_t113_rgb_pdata_t * pdat)
{
	struct t113_tconlcd_reg_t * tcon = (struct t113_tconlcd_reg_t *)pdat->virt_tconlcd;
	uint32_t val;

	val = read32((virtual_addr_t)&tcon->dclk);
	val &= ~(0xf << 28);
	write32((virtual_addr_t)&tcon->dclk, val);

	write32((virtual_addr_t)&tcon->gctrl, 0);
	write32((virtual_addr_t)&tcon->gint0, 0);
}

static void t113_tconlcd_set_timing(struct fb_t113_rgb_pdata_t * pdat)
{
	struct t113_tconlcd_reg_t * tcon = (struct t113_tconlcd_reg_t *)pdat->virt_tconlcd;
	int bp, total;
	uint32_t val;

	val = (pdat->timing.v_front_porch + pdat->timing.v_back_porch + pdat->timing.v_sync_len) / 2;
	write32((virtual_addr_t)&tcon->ctrl, (1 << 31) | (0 << 24) | (0 << 23) | ((val & 0x1f) << 4) | (0 << 0));
	val = sunxi_clk_get_peri1x_rate() * 2 / pdat->timing.pixel_clock_hz;
	write32((virtual_addr_t)&tcon->dclk, (0xf << 28) | (val << 0));
	write32((virtual_addr_t)&tcon->timing0, ((pdat->width - 1) << 16) | ((pdat->height - 1) << 0));
	bp = pdat->timing.h_sync_len + pdat->timing.h_back_porch;
	total = pdat->width + pdat->timing.h_front_porch + bp;
	write32((virtual_addr_t)&tcon->timing1, ((total - 1) << 16) | ((bp - 1) << 0));
	bp = pdat->timing.v_sync_len + pdat->timing.v_back_porch;
	total = pdat->height + pdat->timing.v_front_porch + bp;
	write32((virtual_addr_t)&tcon->timing2, ((total * 2) << 16) | ((bp - 1) << 0));
	write32((virtual_addr_t)&tcon->timing3, ((pdat->timing.h_sync_len - 1) << 16) | ((pdat->timing.v_sync_len - 1) << 0));

	val = (0 << 31) | (1 << 28);
	if(!pdat->timing.h_sync_active)
		val |= (1 << 25);
	if(!pdat->timing.v_sync_active)
		val |= (1 << 24);
	if(!pdat->timing.den_active)
		val |= (1 << 27);
	if(!pdat->timing.clk_active)
		val |= (1 << 26);
	write32((virtual_addr_t)&tcon->io_polarity, val);
	write32((virtual_addr_t)&tcon->io_tristate, 0);
}

static void t113_tconlcd_set_dither(struct fb_t113_rgb_pdata_t * pdat)
{
	struct t113_tconlcd_reg_t * tcon = (struct t113_tconlcd_reg_t *)pdat->virt_tconlcd;

	if((pdat->bits_per_pixel == 16) || (pdat->bits_per_pixel == 18))
	{
		write32((virtual_addr_t)&tcon->frm_seed[0], 0x11111111);
		write32((virtual_addr_t)&tcon->frm_seed[1], 0x11111111);
		write32((virtual_addr_t)&tcon->frm_seed[2], 0x11111111);
		write32((virtual_addr_t)&tcon->frm_seed[3], 0x11111111);
		write32((virtual_addr_t)&tcon->frm_seed[4], 0x11111111);
		write32((virtual_addr_t)&tcon->frm_seed[5], 0x11111111);
		write32((virtual_addr_t)&tcon->frm_table[0], 0x01010000);
		write32((virtual_addr_t)&tcon->frm_table[1], 0x15151111);
		write32((virtual_addr_t)&tcon->frm_table[2], 0x57575555);
		write32((virtual_addr_t)&tcon->frm_table[3], 0x7f7f7777);

		if(pdat->bits_per_pixel == 16)
			write32((virtual_addr_t)&tcon->frm_ctrl, (1 << 31) | (1 << 6) | (0 << 5)| (1 << 4));
		else if(pdat->bits_per_pixel == 18)
			write32((virtual_addr_t)&tcon->frm_ctrl, (1 << 31) | (0 << 6) | (0 << 5)| (0 << 4));
	}
}

static void fb_t113_rgb_init(struct fb_t113_rgb_pdata_t * pdat)
{
	t113_tconlcd_disable(pdat);
	t113_tconlcd_set_timing(pdat);
	t113_tconlcd_set_dither(pdat);
	t113_tconlcd_enable(pdat);
	t113_de_set_mode(pdat);
	t113_de_enable(pdat);
	t113_de_set_address(pdat, pdat->vram[pdat->index]);
	t113_de_enable(pdat);
}

static void fb_present(struct fb_t113_rgb_pdata_t * pdat, void * pixels, int pixlen)
{

	memcpy(pdat->vram[pdat->index], pixels, pixlen);
	// dma_cache_sync(pdat->vram[pdat->index], pdat->pixlen, DMA_TO_DEVICE);
    rt_hw_cpu_dcache_clean(pdat->vram[pdat->index], pdat->pixlen);
	t113_de_set_address(pdat, pdat->vram[pdat->index]);
	t113_de_enable(pdat);
}

static void fb_t113_rgb_clk_reset(void)
{
	uint32_t addr;
	uint32_t val;

	/* Deassert de reset */
	addr = 0x0200160C;
	val = read32(addr);
	val |= (1 << 16);
	write32(addr, val);

	/* Open the de gate */
	addr = 0x02001600;
	val = read32(addr);
	val |= (1 << 31);
	write32(addr, val);

	/* Open the de bus gate */
	addr = 0x0200160C;
	val = read32(addr);
	val |= (1 << 0);
	write32(addr, val);

	/* Select pll-peri2x for de clk */
	addr = 0x02001600;
	val = read32(addr);
	val &= ~(0x7 << 24);
	val |= 0x0 << 24;
	write32(addr, val);

	/* Set clock divide ratio, divided by 6 */
	addr = 0x02001600;
	val = read32(addr);
	val &= ~(0x1f << 0);
	val |= (6 - 1) << 0;
	write32(addr, val);  


    /* Deassert tcon reset */
	addr = 0x02001B7C;
	val = read32(addr);
	val |= (1 << 16);
	write32(addr, val);

	/* Open the tcon gate */
	addr = 0x02001b60;
	val = read32(addr);
	val |= (1 << 31);
	write32(addr, val);

	/* Open the tcon bus gate */
	addr = 0x02001B7C;
	val = read32(addr);
	val |= (1 << 0);
	write32(addr, val);

	/* Select pll-peri2x for tcon clk */
	addr = 0x02001b60;
	val = read32(addr);
	val &= ~(0x7 << 24);
	val |= 0x04 << 24;
	write32(addr, val);

    /* Set clock pre divide ratio, divided by 1 */
	addr = 0x02001b60;
	val = read32(addr);
	val &= ~(0x3 << 8);
	val |= 0x0 << 8;
	write32(addr, val);

	/* Set clock divide ratio, divided by 6 */
	addr = 0x02001b60;
	val = read32(addr);
	val &= ~(0x1f << 0);
	val |= (6 - 1) << 0;
	write32(addr, val); 
}

int fb_t113_rgb_probe(struct fb_t113_rgb_pdata_t * pdat, tcon_panel_t panel)
{
    pdat->virt_de = T113_DE_BASE;
	pdat->virt_tconlcd = (T113_TCONLCD_BASE);

	pdat->width = panel->width;
	pdat->height = panel->height;
	pdat->bits_per_pixel = panel->bits_pixel;
	pdat->bytes_per_pixel = 4;
	pdat->pixlen = pdat->width * pdat->height * pdat->bytes_per_pixel;
	pdat->index = 0;
	pdat->vram[0] = rt_malloc(pdat->pixlen);
	pdat->vram[1] = rt_malloc(pdat->pixlen);
	memset(pdat->vram[0], 0, pdat->pixlen);
	memset(pdat->vram[1], 0, pdat->pixlen);

    pdat->timing.pixel_clock_hz = panel->timing.pixel_clock_hz;
	pdat->timing.h_front_porch = panel->timing.h_front_porch;
	pdat->timing.h_back_porch = panel->timing.h_back_porch;
	pdat->timing.h_sync_len = panel->timing.h_sync_len;
	pdat->timing.v_front_porch = panel->timing.v_front_porch;
	pdat->timing.v_back_porch = panel->timing.v_back_porch;
	pdat->timing.v_sync_len = panel->timing.v_sync_len;
	pdat->timing.h_sync_active = panel->timing.h_sync_active;
	pdat->timing.v_sync_active = panel->timing.v_sync_active;
	pdat->timing.den_active = panel->timing.den_active;
	pdat->timing.clk_active = panel->timing.clk_active;

	fb_t113_rgb_clk_reset();
	fb_t113_rgb_init(pdat);

    return 0;
}

static rt_err_t rt_lcd_control(rt_device_t dev, int cmd, void *args)
{
    struct lcd_device *lcd_drv = (struct lcd_device *)dev;
	struct fb_t113_rgb_pdata_t * pdat;
    rt_uint32_t memcp_tick_tmp;

	pdat = lcd_drv->pdat;

    switch (cmd)
    {
    case RTGRAPHIC_CTRL_RECT_UPDATE:
    {
		fb_present(pdat, lcd_drv->framebuffer, LCD_DRV_FB_SZ);
        break;
    }
    case RTGRAPHIC_CTRL_POWERON:
        break;
    case RTGRAPHIC_CTRL_POWEROFF:
        break;
    case RTGRAPHIC_CTRL_GET_INFO:
        memcpy(args, &lcd_drv->lcd_info, sizeof(struct rt_device_graphic_info));
        break;
    case FBIOGET_FSCREENINFO:
    {
        struct fb_fix_screeninfo *info = (struct fb_fix_screeninfo *)args;
        strncpy(info->id, "lcd", sizeof(info->id));
        info->smem_len    = LCD_DRV_FB_SZ;
#ifdef RT_USING_USERSPACE
        info->smem_start  = (size_t)lwp_map_user_phy(lwp_self(), RT_NULL, lcd_drv->framebuffer_phy, info->smem_len, 1);
#else
        info->smem_start = (size_t)lcd_drv->framebuffer_phy;
#endif
        info->line_length = lcd_drv->lcd_info.width * sizeof(rt_uint32_t);
        memset((void *)info->smem_start, 0, info->smem_len);
    }
    case RTGRAPHIC_CTRL_SET_MODE:
        break;
    }
    rt_kprintf("rt_lcd_control\n");
    return RT_EOK;
}

static void lcd_gpio_config(void)
{
    for (int i = GPIO_PIN_0; i <= GPIO_PIN_21; i++)
    {
        gpio_set_func(GPIO_PORT_D, i, IO_FUN_2);
        gpio_set_drive_level(GPIO_PORT_D, i, DRV_LEVEL_2);
        gpio_set_pull_mode(GPIO_PORT_D, i, PULL_DISABLE);
    }
}

rt_uint32_t *lcd_get_framebuffer(void *dev)
{
    return ((struct lcd_device *)dev)->framebuffer;
}

static rt_err_t rt_lcd_init(rt_device_t dev)
{
    lcd_device_t lcd_drv = (lcd_device_t)dev;
    int format;

    RT_ASSERT(lcd_drv != RT_NULL);

    /* Initializes the GPIO of the lcd device */
    lcd_gpio_config();

	fb_t113_rgb_probe(lcd_drv->pdat, _panel);

    rt_kprintf("rt_lcd_init\n");
    return 0;
}


/* set up the 'lcd_device' and register it */
int rt_hw_lcd_init(void)
{
    struct lcd_device * lcd_drv = &_lcd_device;
    rt_uint32_t *framebuffer = RT_NULL;

    /* the content of tcon control registers can be loaded from a xml file ? */
    _panel = &lcd_config;//load_config_from_xml();
    memset(lcd_drv, 0, sizeof(struct lcd_device));

    /* allocate the framebuffer, the front buffer and the back buffer */
    /* framebuffer */
#ifdef RT_USING_USERSPACE
    framebuffer = rt_pages_alloc(rt_page_bits(LCD_DRV_FB_SZ));
#else
    framebuffer = rt_malloc(LCD_DRV_FB_SZ);
#endif
    if (!framebuffer)
    {
        rt_kprintf("malloc framebuffer %d  fail\n", LCD_DRV_FB_SZ);
        goto out;
    }
    lcd_drv->lcd_info.framebuffer = (rt_uint8_t *)framebuffer;
    lcd_drv->framebuffer = framebuffer;
    lcd_drv->framebuffer_phy = (void*)((size_t)framebuffer + PV_OFFSET);
    memset(framebuffer, 0, LCD_DRV_FB_SZ);
    rt_hw_cpu_dcache_clean(lcd_drv->framebuffer, LCD_DRV_FB_SZ);

    /*
     * The semaphore is used for the synchronization between updating the
     * framebuffer and flushing the screen.
     */
    rt_sem_init(&lcd_drv->lcd_sem, "lcd_sem", 0, RT_IPC_FLAG_FIFO);

    /* the lcd device information defined by RT-Thread */
    lcd_drv->lcd_info.bits_per_pixel = 32;
    lcd_drv->lcd_info.pixel_format = RTGRAPHIC_PIXEL_FORMAT_ARGB888; /* should be coherent to adding layers */
    lcd_drv->lcd_info.framebuffer = (void *)framebuffer;
    lcd_drv->lcd_info.width = LCD_LAYER_WIDTH;
    lcd_drv->lcd_info.height = LCD_LAYER_HEIGHT;

    /* initialize device structure, the type of 'lcd' is 'rt_device' */
    lcd_drv->lcd.type = RT_Device_Class_Graphic;
    lcd_drv->lcd.init = RT_NULL;
    lcd_drv->lcd.open = RT_NULL;
    lcd_drv->lcd.close = RT_NULL;
    lcd_drv->lcd.control = rt_lcd_control;
    lcd_drv->lcd.user_data = (void *)&lcd_drv->lcd_info;

    /* register lcd device to RT-Thread */
    rt_device_register(&lcd_drv->lcd, "lcd", RT_DEVICE_FLAG_RDWR);

	lcd_drv->pdat = rt_malloc(sizeof(struct fb_t113_rgb_pdata_t));
    if(!lcd_drv->pdat)
		return -1;

    rt_lcd_init(&lcd_drv->lcd);

    /* the number of the lcd ? DEFAULT_SCREEN = 0 */
    lcd_drv->use_screen = DEFAULT_SCREEN;

    fb_present(lcd_drv->pdat, framebuffer, LCD_DRV_FB_SZ);

    // lcd_drv->back_buf_info.status = FULL;
    return RT_EOK;
out:
    if (framebuffer)
    {
#ifdef RT_USING_USERSPACE
        rt_pages_free(framebuffer);
#else
        rt_free(framebuffer);
#endif
    }

    return RT_ERROR;
}
INIT_ENV_EXPORT(rt_hw_lcd_init);

static int lcd_draw_point(int args,char *argv[])
{
    int x = 0;
    int y = 0;
    int i, k;
	rt_device_t device;
	struct rt_device_graphic_info lcd_info;
	uint32_t *lcd_framebuffer;

    rt_kprintf("lcd_draw_point\n");

	x = atoi(argv[1]);
    y = atoi(argv[2]);

	device = rt_device_find("lcd");
	if (device == RT_NULL)
	{
		return -1;
	}
	rt_device_control(device, RTGRAPHIC_CTRL_GET_INFO, &lcd_info);

    
    if(x >= lcd_info.width)   x = lcd_info.width  - 1;
    if(y >= lcd_info.height)  y = lcd_info.height - 1;
    if (x < 0) x = 0;
    if (y < 0) y = 0;

    rt_kprintf("Darw point is x:%d,y:%d\n",x,y);

	lcd_framebuffer = lcd_get_framebuffer(device);



    for (i = y - 100; i < y + 100; i++)
    {
        if (i < 0) continue;
        if (i >= lcd_info.width) break;
        for (k = x - 100; k < x + 100; k++)
        {
            if (k < 0) continue;
            if (k >= lcd_info.width) break;

            *((uint32_t *)lcd_framebuffer + lcd_info.width  * i + k) = 0xff00ff00;
        }
    }
	rt_device_control(device, RTGRAPHIC_CTRL_RECT_UPDATE, RT_NULL);

    return 0;
}
MSH_CMD_EXPORT(lcd_draw_point,draw a point on lcd);


